package dnaSequencer;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;

public class HeurystykaNegatywne {
	boolean debug;
	boolean log;

	private final int A = 0;
	private final int C = 1;
	private final int G = 2;
	private final int T = 3;

	private String fileName = null;
	private ArrayList<String> oligonukleotydy = null;
	private int singlePieceLength;
	private int totalLength;

	private ArrayList<DNAPiece>[] front = null;
	private ArrayList<DNAPiece>[] back = null;

	private ArrayList<DNAPiece> DNApieces = null;
	private ArrayList<DNAPiece> DNAcoliding = null;
	private DNAPiece DNAfinal = null;
	private int usedWords;

	public HeurystykaNegatywne(String fileName, String params) {
		this.fileName = fileName;
		if (params != null) {
			if (params.contains("l"))
				log = true;
			else
				log = false;

			if (params.contains("d"))
				debug = true;
			else
				debug = false;

		} else {
			log = false;
			debug = false;
		}
	}

	public void run() {
		load();
		buildDNA();
		insertCollidingDNA();
		pruneDNA();
	}

	private void load() {
		try {
			oligonukleotydy = ResultsParser.loadResultsFromFile(fileName);
			singlePieceLength = ResultsParser.getL();
			totalLength = ResultsParser.getN();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	private int countHashFront(DNAPiece oligonukleotyd, int length) {
		return countHash(oligonukleotyd.getDNA(), length - 1, 0);
	}

	private int countHashBack(DNAPiece oligonukleotyd, int length) {
		return countHash(oligonukleotyd.getDNA(), oligonukleotyd.getDNA()
				.length() - 1, oligonukleotyd.getDNA().length() - length);
	}

	private int countHash(String oligonukleotyd, int start, int finish) {
		int mnoznik = 1;
		int wartosc = 0;
		for (int i = start; i >= finish; i--) {
			char nukleotyd = oligonukleotyd.charAt(i);
			switch (nukleotyd) {
			case 'A':
				wartosc += A * mnoznik;
				break;
			case 'C':
				wartosc += C * mnoznik;
				break;
			case 'G':
				wartosc += G * mnoznik;
				break;
			case 'T':
				wartosc += T * mnoznik;
				break;
			}
			mnoznik *= 4;
		}
		return wartosc;

	}

	private void sortInArrays(ArrayList<DNAPiece> dane, int length) {
		front = new ArrayList[(int) Math.pow(4, length)];
		back = new ArrayList[(int) Math.pow(4, length)];

		for (DNAPiece a : dane) {
			int hashFront = countHashFront(a, length);
			int hashBack = countHashBack(a, length);
			if (front[hashFront] == null) {
				front[hashFront] = new ArrayList<DNAPiece>();
			}
			front[hashFront].add(a);
			if (back[hashBack] == null) {
				back[hashBack] = new ArrayList<DNAPiece>();
			}
			back[hashBack].add(a);
		}
	}

	private void show() {
		if (log) {

			for (DNAPiece a : DNAcoliding)
				System.out.println(a.getDNA());
			System.out.println(DNAcoliding.size());

		}
	}

	private void buildDNA() {
		ArrayList<DNAPiece> zbiorDanych = new ArrayList<DNAPiece>();

		for (String a : oligonukleotydy)
			zbiorDanych.add(new DNAPiece(singlePieceLength, a));

		int zakladka = singlePieceLength - 1;
		while (zbiorDanych.size() > 1 && zakladka > 0) {
			DNAcoliding = new ArrayList<DNAPiece>();
			DNApieces = new ArrayList<DNAPiece>();
			if (debug || log)
				System.out.println("Zaczynam liczyc dla " + zakladka
						+ "(na starcie mam " + zbiorDanych.size() + " słów)");

			sortInArrays(zbiorDanych, zakladka);
			if (debug)
				System.out.println("Posortowałem tablice");

			int konflikty = 0;
			for (int x = 0; x < front.length; x++) {
				if (front[x] != null && front[x].size() > 1) {
					konflikty += front[x].size();
					for (DNAPiece a : front[x]) {
						if (zbiorDanych.contains(a)) {
							DNAcoliding.add(a);
							zbiorDanych.remove(a);
						}
					}
					front[x] = null;
				}
			}
			if (log)
				System.out.println("Konflikty front " + konflikty);

			konflikty = 0;
			for (int x = 0; x < back.length; x++) {
				if (back[x] != null && back[x].size() > 1) {
					konflikty += back[x].size();
					for (DNAPiece a : back[x]) {
						if (zbiorDanych.contains(a)) {
							DNAcoliding.add(a);
							zbiorDanych.remove(a);
						}

					}
					back[x] = null;
				}
			}
			if (log)
				System.out.println("Konflikty back " + konflikty);

			while (zbiorDanych.size() != 0) {
				DNAPiece tmpDNA = new DNAPiece(singlePieceLength);

				tmpDNA.add(zbiorDanych.get(0), DNAPiece.FRONT);
				zbiorDanych.remove(0);

				// Doklejanie z tylu
				if (debug)
					System.out.println("Do tyłu");
				while (zbiorDanych.size() > 0) {
					if (debug)
						System.out.print(tmpDNA.getDNA());

					int hashValue = countHashBack(tmpDNA, zakladka);
					if (front[hashValue] != null) {

						DNAPiece a = front[hashValue].get(0);
						if (zbiorDanych.contains(a)) {
							if (debug)
								System.out.println(" + " + a.getDNA());
							tmpDNA.add(a, DNAPiece.BACK);
							zbiorDanych.remove(a);
							front[hashValue] = null;
						} else {
							front[hashValue] = null;
							break;
						}

					} else {
						break;
					}

				}

				// Dokladanie z przodu
				if (debug)
					System.out.println("\nDo przodu");
				while (zbiorDanych.size() > 0) {
					if (debug)
						System.out.print(tmpDNA.getDNA());
					int hashValue = countHashFront(tmpDNA, zakladka);
					if (back[hashValue] != null) {

						DNAPiece a = back[hashValue].get(0);
						if (zbiorDanych.contains(a)) {
							if (debug)
								System.out.println(" + " + a.getDNA());
							tmpDNA.add(a, DNAPiece.FRONT);
							zbiorDanych.remove(a);
							back[hashValue] = null;
						} else {
							back[hashValue] = null;
							break;
						}

					} else {
						break;
					}

				}
				if (debug)
					System.out.println("\nDodaję: " + tmpDNA.getDNA()
							+ "(zostało " + zbiorDanych.size() + ")");
				DNApieces.add(tmpDNA);
			}

			zbiorDanych = new ArrayList<DNAPiece>(DNApieces);
			for (DNAPiece a : DNAcoliding)
				zbiorDanych.add(a);
			zakladka--;

			if (debug) {
				try {
					System.in.read();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

		}

	}

	private void insertCollidingDNA() {
		ArrayList<DNAPiece> workingSet = new ArrayList<DNAPiece>();
		DNAPiece DNAbest = null;		
		
		for (DNAPiece a : DNApieces)
			workingSet.add(a);
		for (DNAPiece a : DNAcoliding)
			workingSet.add(a);

		for (int iteracja = 0; iteracja < workingSet.size(); iteracja++) {
			
			DNApieces = new ArrayList<DNAPiece>();
			DNAcoliding = new ArrayList<DNAPiece>();
			
			for (int i = 0; i < workingSet.size(); i++){
				if (i == iteracja)
					DNApieces.add(new DNAPiece(workingSet.get(i)));
				else
					DNAcoliding.add(new DNAPiece(workingSet.get(i)));
			}
			
		
			// Wstawienie tych ktore pasuje bez wydluzenia ciagu
			while (DNAcoliding.size() > 0) {
				DNAPiece a = DNAcoliding.get(0);
				if (DNApieces.get(0).getDNA().contains(a.getDNA())) {
					DNApieces.get(0).insertFullOK(a);
					DNAcoliding.remove(a);
				}
				// wstawianie w jak najlepszym miejscu tych ktore pozostaly
				// (jesli pozostaly)
				else {
					int usuniety = DNApieces.get(0).insertBestFit(DNAcoliding);
					DNAcoliding.remove(usuniety);
				}
			}
			if (DNAbest == null || DNAbest.getUseWordsCount() > DNApieces.get(0).getUseWordsCount())
				DNAbest = DNApieces.get(0);
			
			if (DNAbest.getDNALength() <= totalLength)
				break;
		}


		DNAfinal = DNAbest;

		int i = 0;


	}

	private void pruneDNA() {
		int actualLength = 0;

		actualLength = DNAfinal.getDNALength();
		while (actualLength > totalLength) {
			if (DNAfinal.getZakladkaLength(DNAPiece.BACK) < DNAfinal
					.getZakladkaLength(DNAPiece.FRONT))
				DNAfinal.removeOne(DNAPiece.BACK);
			else
				DNAfinal.removeOne(DNAPiece.FRONT);
			actualLength = DNAfinal.getDNALength();
		}
		if (log)
			System.out.println("Skonczylem z DNA " + DNAfinal.getDNA());

		System.out.println("Użyłem słów: " + DNAfinal.getUseWordsCount() + "/"
				+ oligonukleotydy.size());

		System.out.println(fileName + ": Sumaryczna długość to: "
				+ actualLength + "/" + totalLength);

	}
}
